
#if defined(WIN32) || defined(WIN64)
#pragma unmanaged
#endif

#include <Liberty3D/Engine/IAssetFile.h>
#include <Liberty3D/Engine/Conversion.h>

#include <Windows.h>

#include <streambuf>
#include <fstream>
#include <sstream>

using namespace Liberty::Engine;

namespace Liberty { namespace Engine { namespace Internal {
    class AssetFileModule : public IAssetFile {
    protected:
        HINSTANCE _hInstance;

    protected:
        class resbuf : public std::streambuf {
        protected:
            HGLOBAL _hGlobal;
            LPVOID  _data;
            LPVOID  _pointer;
            LPVOID  _end;
            size_t  _size;

        public:
            resbuf(HGLOBAL hGlobal, LPVOID data, size_t size) : std::streambuf(), _hGlobal(hGlobal), _data(data), _pointer(data), _size(size) {
                _end = ((LPBYTE)_data) + size;
            }

            virtual ~resbuf() {
                FreeResource(_hGlobal);
            }

        protected:
            virtual std::streampos seekoff(std::streamoff off, std::ios::seekdir way, std::ios::openmode) {
                switch (way) {
                default:
                case std::ios::beg: _pointer = ((LPBYTE)_data)    + off; break;
                case std::ios::cur: _pointer = ((LPBYTE)_pointer) + off; break;
                case std::ios::end: _pointer = ((LPBYTE)_end)     + off; break;
                }

                if (_pointer > _end) {
                    _pointer = _end;
                }

                return ((LPBYTE)_pointer) - ((LPBYTE)_data);
            }

            virtual std::streampos seekpos(std::streamoff off, std::ios::openmode mode) {
                return seekoff(off, std::ios::beg, mode);
            }

            virtual std::streamsize showmanyc() {
                return ((LPBYTE)_end) - ((LPBYTE)_pointer);
            }

            virtual std::streamsize xsgetn(char* s, std::streamsize n) {
                std::streamsize remain(showmanyc());

                if (n > remain) {
                    n = remain;
                }

                memcpy(s, _pointer, (size_t)n);
                _pointer = ((LPBYTE)_pointer) + n;
                return n;
            }
        };

        class FindParameters {
        public:
            const std::wstring&       folder;
            std::vector<std::string>& entries;

            FindParameters(const std::wstring& f, std::vector<std::string>& e) : folder(f), entries(e) {
            }
        };

        static BOOL CALLBACK EnumProc(
                                HMODULE  hModule,
                                LPCTSTR  lpszType,
                                LPTSTR   lpszName,
                                LPARAM   lParam) {
            FindParameters& params = *((FindParameters*)lParam);

            if (IS_INTRESOURCE(lpszName)) {
                return TRUE;
            }

            if (_wcsnicmp(lpszName, params.folder.c_str(), params.folder.length()) == 0) {
                params.entries.push_back(UTF16_To_UTF8(lpszName));
            }

            return TRUE;
        }

    public:
        AssetFileModule(void* hInstance) : _hInstance((HINSTANCE)hInstance) {
        }

        virtual bool exists(const std::string& resourceName) const {
            HRSRC hRes = FindResource(_hInstance, UTF8_To_UTF16(resourceName).c_str(), L"DATA");

            return (hRes != NULL);
        }

        virtual std::streambuf* open(const std::string& resourceName, std::ios::openmode mode) const {
            HRSRC hRes = FindResource(_hInstance, UTF8_To_UTF16(resourceName).c_str(), L"DATA");

            if (hRes == NULL) {
                return NULL;
            }

            HGLOBAL hGlobal = LoadResource(_hInstance, hRes);

            if (hGlobal == NULL) {
                return NULL;
            }

            LPVOID pData = LockResource(hGlobal);

            if (pData == NULL) {
                FreeResource(hGlobal);
                return NULL;
            }

            return new resbuf(hGlobal, pData, SizeofResource(_hInstance, hRes));
        }

        virtual void list(const std::string& folder, std::vector<std::string>& entries) const {
            FindParameters data(UTF8_To_UTF16(folder), entries);
            
            EnumResourceNames(_hInstance, L"DATA", EnumProc, (LONG_PTR)&data);
        }
    };
}}}

std::shared_ptr<IAssetFile> IAssetFile::assetFileForModule(void* hInstance) {
    return std::shared_ptr<IAssetFile>(new Liberty::Engine::Internal::AssetFileModule(hInstance));
}
